package cn.tedu.charging.device.service.impl;

import cn.tedu.charging.common.pojo.param.UpdateGunStatusParam;
import cn.tedu.charging.device.dao.mapper.StationMapper;
import cn.tedu.charging.device.pojo.po.StationPO;
import cn.tedu.charging.device.pojo.vo.StationVO;
import cn.tedu.charging.device.service.DeviceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * 设备业务实现类
 */
@Slf4j
@Service
public class DeviceServiceImpl implements DeviceService {
    //查询附近的充电站
    @Override
    public List<StationVO> nearStation(Double longitude, Double latitude, Double distance) {
        //redisTemplate.opsForGeo()   操作GEO类型的Redis命令
        GeoOperations geoOperations = redisTemplate.opsForGeo();

        // opsForValue() 操作字符串类型的数据
        //泛型  key String    ,value  StationPO
        ValueOperations<String, StationPO> valueOperations = redisTemplate.opsForValue();

        //通过用户的入参 经纬度  创建一个用户所在位置的点
        Point point = new Point(longitude, latitude);
        //通过用户的入参 距离  创建一个距离对象    Metrics 导包选择  import org.springframework.data.geo.Metrics;
        // Metrics.KILOMETERS  单位：公里 千米
        //redius 半径\  范围
        Distance redius = new Distance(distance, Metrics.KILOMETERS);
        //通过  用户的位置 和 要查找的范围  创建一个圆形区域
        Circle circle = new Circle(point, redius);
        //固定写法 通过Redis的GEO类型的命令 查找  在指定区域内的所有场站数据 放到 stations中
        GeoResults<RedisGeoCommands.GeoLocation<Integer>> stations = geoOperations.radius(STATION_GEO_KEY, circle);
        //查到的数据要  转换为  我们要的结果  创建一个集合  用来存储结果
        List<GeoResult<RedisGeoCommands.GeoLocation<Integer>>> content = stations.getContent();
        //定义返回结果  vo  list
        List<StationVO> list = new ArrayList<>();

        for (GeoResult<RedisGeoCommands.GeoLocation<Integer>> geoLocationGeoResult : content) {
            //name 表示的是 存储的时候那个member  存一起是场站信息  分开存是场站id
            Integer stationId = geoLocationGeoResult.getContent().getName();
            String key = STATION_DETAIL_KEY + stationId;
            //通过key  查询value
            StationPO stationPO = valueOperations.get(key);
            //创建一个vo对象
            StationVO stationVO = new StationVO();
            //把stationPO数据 赋值给stationVO  拷贝
            BeanUtils.copyProperties(stationPO, stationVO);
            //把stationVO数据 添加到list中
            list.add(stationVO);
        }
        //返回结果
        return list;
//        ArrayList<StationVO> stationVOS = new ArrayList<>();
//        StationVO stationVO = new StationVO();
//        stationVO.setId(1);
//        stationVO.setStationCode("1");
//        stationVO.setStationName("充电站1");
//        stationVO.setStationLat(1.0);
//        stationVO.setStationLng(1.0);
//        stationVOS.add(stationVO);
//        return stationVOS;
    }

    //存储场站位置信息的Redis key
    private String STATION_GEO_KEY="stations_geo_key";
    //存储场站详情信息的Redis key
    private String STATION_DETAIL_KEY="stations_detail_";

    @Autowired
    private StationMapper stationMapper;

    @Override
    public StationVO stationDetailById(Integer stationId) {
        StationPO stationPOById = getStationPOById(stationId);
        if (stationPOById != null) {
            StationVO stationVO = new StationVO();
            //2.7、赋值返回结果
            stationVO.setId(stationPOById.getId());
            //可以使用BeanUtils.copyProperties()方法
            //Spring提供的工具类：复制功能、（源数据，目标对象）
            BeanUtils.copyProperties(stationPOById, stationVO);
            return stationVO;
        }
        return null;
    }

    /**
     * 从数据库查询所有场站信息
     */
    private List<StationPO> getAllStations() {
        //从数据库查询场站信息  selectList 会把所有场站信息都查出来
        return stationMapper.selectList(null);
    }

    /**
     * 保存场站信息到Redis的GEO中
     */
    private void saveStationPO2RedisGeo(List<StationPO> allStations) {
        //opsForGeo() 操作GEO类型的Redis命令
        GeoOperations geoOperations = redisTemplate.opsForGeo();
        // opsForValue() 操作字符串类型的数据
        //泛型  key String    ,value  StationPO
        ValueOperations<String, StationPO> valueOperations = redisTemplate.opsForValue();

        for (StationPO stationPO : allStations) {
            Double stationLng = stationPO.getStationLng();
            Double stationLat = stationPO.getStationLat();
            //创建Point对象 点 充电站的经纬度
            Point point = new Point(stationLng, stationLat);
            //保存到Redis的GEO中
            //Long add(K key, Point point, M member); //GEOADD 命令
            //key  stations
            //point  充电站的经纬度 点
            //member 充电站的信息 把场站的位置信息和基本信息存到了一起
            //geoOperations.add("stations", point, stationPO);
            //member 充电站的id 把场站的位置信息和基本信息分开存 GEO存位置 String 存储基本信息
            //基本信息 可以复用 给 充电站详情使用
            geoOperations.add(STATION_GEO_KEY, point, stationPO.getId());
            //设置key 也就是存有场站信息的key
            String key = STATION_DETAIL_KEY + stationPO.getId();
            //valueOperations.set    保存key value 到Redis
            valueOperations.set(key, stationPO);
            //此时for循环之后，位置信息存了"stations"   、场站基本信息也存了"station_detail_"
            //方案1：吧场站的位置信息和基本信息存到了一起

        }
    }

    /**
     * 初始化场站数据
     * //1 从数据库查询场站信息
     * //2 保存场站信息到Redis的GEO中
     */
    @Override
    public void initStationData() {
        //从数据库查询场站信息
        List<StationPO> allStations = getAllStations();
        //保存场站信息到Redis的GEO中
        saveStationPO2RedisGeo(allStations);
    }

    /**
     * 更新充电枪状态
     * @param updateGunStatusParam
     * @return
     */
    @Override
    public Boolean updateGunStatus(UpdateGunStatusParam updateGunStatusParam) {
        //更新抢状态 charging_gun_info TODO
        return true;
    }


    /**
     * 获取充电站详情
     * 数据库 和 Redis配合的逻辑 CacheAside 旁路缓存
     * 1 获取Redis缓存
     * 2 如果Redis缓存有数据，则返回Redis缓存数据
     * 3 如果Redis缓存没有数据，则从数据库中获取数据
     * 4 如果数据库有数据，保存数据到Redis 缓存中，返回数据库中的数据
     * 5 如果数据库没有数据 则返回null
     *
     * @param stationId
     * @return
     */
    private StationPO getStationPOById(Integer stationId) {
        // 1、通过充电站id 从redis中查询
        StationPO stationByRedisPO = getStationByRedis(stationId);
        // 2、判断Redis是否有数据
        if (stationByRedisPO != null) {
            //2.1、如果有数据，直接返回Redis中的数据
            return stationByRedisPO;
        } else {
            //2.3、如果没有数据，从数据库查询
            StationPO stationByDBPO = getStationByDB(stationId);
            //2.4、判断数据库是否有数据
            if (stationByDBPO != null) {
                //2.5、如果数据库有数据
                //2.6、将数据库中的数据 保存到Redis中   、这一步很重要
                saveStationPO2Redis(stationByDBPO);
                //2.7、返回数据库中的数据
                return stationByDBPO;
            }
        }
        return null;
    }

    /**
     * 根据id,从数据库查询充电站的详情
     *
     * @param stationId 充电站id
     * @return 充电站详情
     */
    private StationPO getStationByDB(Integer stationId) {
        StationPO stationPO = stationMapper.selectById(stationId);
        return stationPO;
    }

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 根据id,从redis查询充电站的详情
     *
     * @param stationId 充电站id
     * @return 充电站详情
     * //redisTemplate 提供了很多 ops  操作不同的数据类型
     * //opsForValue() 操作字符串
     * //opsForHash() 操作哈希
     * //opsForList() 操作列表
     * //opsForSet() 操作集合
     * //opsForZSet() 操作有序集合
     */
    private StationPO getStationByRedis(Integer stationId) {
        //      opsForValue() 操作字符串类型的数据
        //泛型  key String    ,value  StationPO
        ValueOperations<String, StationPO> valueOperations = redisTemplate.opsForValue();
        //设置key 也就是存有场站信息的key
        String key = STATION_DETAIL_KEY + stationId;
        //valueOperations.get(key) 从redis中根据key查询value
        StationPO stationPO = valueOperations.get(key);
        return stationPO;
    }

    /**
     * 将充电站详情保存到redis中
     *
     * @param stationPO 充电站详情
     */
    private void saveStationPO2Redis(StationPO stationPO) {
        //                                        opsForValue() 操作字符串类型的数据
        //泛型  key String    ,value  StationPO
        ValueOperations<String, StationPO> valueOperations = redisTemplate.opsForValue();
        //设置key 也就是存有场站信息的key
        String key = STATION_DETAIL_KEY + stationPO.getId();
        //valueOperations.set    保存key value 到Redis
        valueOperations.set(key, stationPO);

    }


}
